package com.inspire.ijc.agent.utils;

import com.inspire.ijc.agent.IjcAgent;
import com.inspire.ijc.agent.storage.AllContext;
import com.inspire.ijc.agent.storage.TypeConstants;
import com.inspire.ijc.agent.storage.entity.CallMethod;
import com.inspire.ijc.agent.storage.entity.CallThread;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author ：inspire
 * @date ：Created in 2023/4/13 14:35
 * @description：
 * @modified By：inspire
 * @version:
 */
public class CallGraphUtil {

    public static void callGraph(CallThread callThread,int type){

        StringBuffer sb=new StringBuffer();
        List<CallMethod> printCallMethods = AllContext.PRINTLIST.get(callThread.getThreadId());
        Map<Integer, Integer> methodTimesMap = callThread.getMethodTimesMap();
        Date startTime = callThread.getStartTime();
        List<Integer> firstShow=new ArrayList<Integer>();
        AllContext.PRINTLIST.remove(callThread.getThreadId());
        AllContext.CALLTHREADLIST.remove(callThread);

        Collections.sort(printCallMethods);

        long reqCostTime = printCallMethods.get(0).getCostTime();
        if(type==TypeConstants.normal){
            if(reqCostTime>IjcAgent.timeout_V){
                sb.append("\nijc-info-timeout");
            }else{
                sb.append("\nijc-info");
            }
        }else if(type==TypeConstants.exception){
            sb.append("\nijc-info-exception");
        }
        sb.append(" -> time:"+FileUtils.convertTime(startTime,FileUtils.DATE_PATTERN_NOMARK)+",ThreadId:"+callThread.getThreadId()+",ThreadName:"+callThread.getThreadName()+"\n");

        boolean graphError=false;
        for(CallMethod callMethod:printCallMethods){
            int methodId= callMethod.getId();
            if(callMethod.getDepth()<0){
                graphError=true;
                break;
            }
            int i=0;
            while (i!=callMethod.getDepth()){
                i++;
                sb.append("+---");
            }
            if(firstShow.contains(methodId)){
                sb.append("["+callMethod.getCostTime()+"ms] "+callMethod.getClassName()+"."+callMethod.getMethodName()+"\n");
            }else{//函数第一次出现打印总调用数
                firstShow.add(methodId);
                Integer times = methodTimesMap.get(methodId);
                sb.append("["+callMethod.getCostTime()+"ms] "+callMethod.getClassName()+"."+callMethod.getMethodName()+" "+times+" times\n");
            }

        }
        if(!graphError){//目前暂未出现绘制栈的深度错误
            FileUtils.outputContentToFile(sb,"ijc_mcs_"+ IjcAgent.IJCBOOTTIME);
        }else{
            System.out.println("Drawing stack diagram error!");
        }

    }

    /*
    一般不会有超过3s的业务请求，ijc认为最近的push时间超过5s则视为出现异常
    ijc使用最近的push时间而不是pop时间作为判定时间，是考虑有的业务显式的调用的异常（如throw new RuntimeException();）可以采用pop，但是隐式的异常无法使用pop
     */
    public static void printExceptionCallGraph(){
        for(CallThread callThread:AllContext.CALLTHREADLIST){
            if(nanosToMillis(System.nanoTime()- callThread.getNearPushTime())>5000){//最近的pop时间距离当前系统时间超过5000毫秒视为出现异常
                QueueToStack<CallMethod> callMethodsByPush = AllContext.THREADCALL.get(callThread.getThreadId());
                while (!callMethodsByPush.empty()){
                    AllContext.PRINTLIST.get(callThread.getThreadId()).add(callMethodsByPush.pop());//将所有已push但因为异常后续无法pop的CallMethod对象放入打印队列
                }

                //删除该线程的集合
                AllContext.THREADCALL.remove(callThread.getThreadId());
                //输出调用栈
                callGraph(callThread, TypeConstants.exception);
            }
        }
    }

    public static long nanosToMillis(long duration) {
        return TimeUnit.NANOSECONDS.toMillis(duration);
    }
}
